package cz.cesnet.cloud.occi.api.http.auth; import cz.cesnet.cloud.occi.api.Authentication; import cz.cesnet.cloud.occi.api.Client; import cz.cesnet.cloud.occi.api.exception.AuthenticationException; import cz.cesnet.cloud.occi.api.exception.CommunicationException; import cz.cesnet.cloud.occi.api.http.HTTPConnection; import cz.cesnet.cloud.occi.api.http.HTTPHelper; import java.io.File; import java.io.FileInputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStreamReader; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.Security; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import javax.net.ssl.SSLContext; import org.apache.http.HttpHost; import org.apache.http.HttpStatus; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpHead; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.SSLContexts; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.params.HttpConnectionParams; import org.apache.http.util.EntityUtils; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Abstract class representing HTTP authentication methods. Lets set either * directory path or file containing CAs and uses them during establishing of * connection. * * @author Michal Kimle <kimle.michal@gmail.com> */ public abstract class HTTPAuthentication implements Authentication { private static final Logger LOGGER = LoggerFactory.getLogger(HTTPAuthentication.class); private HttpHost target; private HTTPConnection connection; private CredentialsProvider credentialsProvider; private String CAPath; private String CAFile; /** * Returns server that authentication is run against. * * @return */ public HttpHost getTarget() { return target; } /** * Sets server to run authentication against. * * @param target server */ public void setTarget(HttpHost target) { this.target = target; } public HTTPConnection getConnection() { return connection; } public void setConnection(HTTPConnection connection) { this.connection = connection; } public CredentialsProvider getCredentialsProvider() { return credentialsProvider; } public void setCredentialsProvider(CredentialsProvider credentialsProvider) { this.credentialsProvider = credentialsProvider; } /** * Returns path to the custom CA directory. * * @return path to the custom CA directory */ public String getCAPath() { return CAPath; } /** * Sets path to custom CA directory. * * @param CAPath path to custom CA directory */ public void setCAPath(String CAPath) { this.CAPath = CAPath; } /** * Returns path to custom CA file. * * @return path to custom CA file */ public String getCAFile() { return CAFile; } /** * Sets path to custom CA file * * @param CAFile path to custom CA file */ public void setCAFile(String CAFile) { this.CAFile = CAFile; } @Override public abstract String getIdentifier(); @Override public abstract Authentication getFallback(); /** * Creates a ssl context with custom CAs if set. * * @return ssl context * @throws AuthenticationException */ protected SSLContext createSSLContext() throws AuthenticationException { Security.addProvider(new BouncyCastleProvider()); KeyStore keyStore = loadCAs(); try { SSLContext sslContext; if (keyStore == null) { sslContext = SSLContexts.createSystemDefault(); } else { sslContext = SSLContexts.custom().loadTrustMaterial(keyStore).build(); } return sslContext; } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException ex) { throw new AuthenticationException(ex); } } @Override public void authenticate() throws CommunicationException { SSLContext sslContext = createSSLContext(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext); LOGGER.debug("Running authentication..."); try { RequestConfig defaultRequestConfig = RequestConfig.custom() .setSocketTimeout(10000) .setConnectTimeout(10000) .setConnectionRequestTimeout(10000) .build(); HttpClientBuilder builder = HttpClients.custom() .setDefaultCredentialsProvider(credentialsProvider) .setSSLSocketFactory(sslsf) .setDefaultRequestConfig(defaultRequestConfig); if (LOGGER.isDebugEnabled()) { builder.disableContentCompression(); } CloseableHttpClient client = builder.build(); connection.setClient(client); HttpHead httpHead = HTTPHelper.prepareHead(Client.MODEL_URI, connection.getHeaders(), connection.getPrefix()); try (CloseableHttpResponse response = connection.getClient().execute(target, httpHead, connection.getContext())) { if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { Authentication fallback = getFallback(); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED && fallback != null) { if (fallback instanceof KeystoneAuthentication) { LOGGER.debug("Running Keystone fallback..."); KeystoneAuthentication ka = (KeystoneAuthentication) fallback; ka.setOriginalResponse(response); ka.authenticate(); } else { throw new AuthenticationException("unknown fallback method"); } } else { if (response.getEntity() == null) { LOGGER.error("Response: {}\nHeaders: {}\nBody:\n", response.getStatusLine().toString(), response.getAllHeaders()); } else { LOGGER.error("Response: {}\nHeaders: {}\nBody: {}", response.getStatusLine().toString(), response.getAllHeaders(), EntityUtils.toString(response.getEntity())); } throw new AuthenticationException(response.getStatusLine().toString()); } } } } catch (IOException ex) { throw new CommunicationException(ex); } } /** * Loads custom CAs either from file or directory. If both set, CA file has * higher priority. * * @return keystore with custom CAs loaded * @throws AuthenticationException */ protected KeyStore loadCAs() throws AuthenticationException { KeyStore keyStore = null; if (CAFile != null && !CAFile.isEmpty()) { keyStore = loadCAsFromFile(); } else if (CAPath != null && !CAPath.isEmpty()) { keyStore = loadCAsFromPath(); } return keyStore; } private KeyStore loadCAsFromFile() throws AuthenticationException { try { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); FileInputStream instream = new FileInputStream(new File(CAFile)); trustStore.load(instream, null); return trustStore; } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException ex) { throw new AuthenticationException(ex); } } private KeyStore loadCAsFromPath() throws AuthenticationException { try { File CADir = new File(CAPath); if (!CADir.isDirectory()) { throw new AuthenticationException("'" + CAPath + "' is not a directory."); } FilenameFilter fileNameFilter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { if (name.lastIndexOf('.') > 0) { int lastIndex = name.lastIndexOf('.'); String str = name.substring(lastIndex); if (str.equals(".pem")) { return true; } } return false; } }; File[] certs = CADir.listFiles(fileNameFilter); KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); ks.load(null); List<Certificate> rootCertificates = new ArrayList<>(); PEMReader reader; for (File cert : certs) { reader = new PEMReader(new InputStreamReader(new FileInputStream(cert))); rootCertificates.add((X509Certificate) reader.readObject()); } for (Certificate cert : rootCertificates) { X509Certificate x509Cert = (X509Certificate) cert; ks.setCertificateEntry(x509Cert.getSubjectX500Principal().getName(), x509Cert); LOGGER.debug("adding certificate: " + x509Cert.getSubjectX500Principal().getName()); } return ks; } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException ex) { throw new AuthenticationException(ex); } } }